﻿// http://www.eggheadcafe.com/articles/parsinghtml.asp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using mshtml;
using jTest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Xml.Linq;

namespace jTest
{
	/// <summary>
	/// The document used to execute tests.
	/// </summary>
	public class jTestDocument
	{
		/// <summary>
		/// Only create an instance of the jTest object internally.
		/// </summary>
		private jTestDocument()
		{ }

		/// <summary>
		/// The <see cref="mshtml.HTMLDocument"/> to perform test apon.
		/// </summary>
		private HTMLDocument _document;

        public HTMLDocument Document 
        {
            get { return _document; }
        }

		/// <summary>
		/// Load the test document.
		/// </summary>
		/// <param name="location">The location to test.</param>
		/// <returns></returns>
		public static jTestDocument Test(Uri location)
		{
			jTestDocument source = new jTestDocument();
			source._document = HtmlDocumentHelper.DownloadDocument(location, null, true);
			
			return source;
		}

		public static jTestDocument Test(String relitiveUrl, TestContext context)
		{
			// Check for valid arguments
			if (String.IsNullOrWhiteSpace(relitiveUrl))
				throw new ArgumentNullException("relitiveUrl");
			if (relitiveUrl.StartsWith("/")
				|| relitiveUrl.StartsWith("htt", StringComparison.CurrentCultureIgnoreCase))
				throw new ArgumentException("Use a relitive Url");

			if (context == null)
				throw new ArgumentNullException("context");

			// Find and create the url
			foreach (String key in context.Properties.Keys)
			{
				if (key.StartsWith("AspNetDevelopmentServer"))
				{
					Uri location = new Uri(context.Properties[key].ToString() + relitiveUrl);
					jTestDocument source = new jTestDocument();
					source._document = HtmlDocumentHelper.DownloadDocument(location, null, true);

					return source;
				}
			}

			// If nothing is found lets make
			throw new InvalidOperationException("Could not find AspNetDevelopmentServer in context, make sure to use the use the AspNetDevelopmentServerAttribute.");
		}

		/// <summary>
		/// Check scrape results for a tag attribute and value.
		/// </summary>
		/// <param name="key">The attribute name.</param>
		/// <param name="results">The results to search through.</param>		
		/// <returns>True if the attribute value is found, false if not.</returns>
		private static Boolean ContainsValidAttribute(String key, String value, IEnumerable<ScrapeResult> results)
		{
			Boolean containsValidAttribute = false;
			foreach (ScrapeResult result in results)
			{
				if (result.Element.Attributtes().ContainsKey(key)
					&& result.Element.Attributtes().ContainsValue(value))
				{
					containsValidAttribute = true;
				}
			}
			return containsValidAttribute;
		}

		/// <summary>
		/// Check scrape results for a tag value.
		/// </summary>
		/// <param name="value">The value to check.</param>
		/// <param name="results">The results to search through.</param>
		/// <returns>True if the value is found, false if not.</returns>
		private static Boolean ContainsValue(String value, IEnumerable<ScrapeResult> results)
		{
			String actual = null;
			String failedResults = null;
			foreach (ScrapeResult result in results)
			{
				actual = result.Element.innerHTML.Trim().ToUpperInvariant();
				String expected = value.Trim().ToUpperInvariant();				

				if (!actual.Contains(expected))
				    failedResults = String.Format("Actual: {0} - Expected: {1}\n", actual, expected);				
			}

            //if (!String.IsNullOrEmpty(failedResults))
            //    Assert.Fail(String.Format(failedResults));

			return String.IsNullOrEmpty(failedResults);
		}

		/// <summary>
		/// Test the number of tags of a given selector.
		/// </summary>
		/// <param nameer="Selector">The jQuery selector to find the tag.</param>
		public void HasTag(String selector, Int32 count, String message)
		{
			IEnumerable<ScrapeResult> results = GetSelectorResults(selector, message);

			Int32 actualCount = results.Count();
			String countMessage = String.Format("{0} : Actual count = {1}", message, actualCount);
			Assert.IsTrue(actualCount == count, countMessage);
		}

		/// <summary>
		/// Test an attribute value.
		/// </summary>
		/// <param name="key">The name of the attribute to test.</param>
		/// <param name="value">The value to check for the test.</param>
		public void HasAttr(String selector, String key, String value, String message)
		{
			IEnumerable<ScrapeResult> results = GetSelectorResults(selector, message);
			Boolean containsValidAttribute = ContainsValidAttribute(key, value, results);

			Assert.IsTrue(containsValidAttribute, message);
		}

		/// <summary>
		/// Test the value of a html tag.
		/// </summary>
		/// <param name="Selector">The jQuery selector to find the tag.</param>
		/// <param name="value">The value to test.</param>
		public void HasVal(String selector, String value, String message)
		{
			IEnumerable<ScrapeResult> results = GetSelectorResults(selector, message);
			Assert.IsTrue(ContainsValue(value, results), message);		
		}
	
		private IEnumerable<ScrapeResult> GetSelectorResults(String selector, String message)
		{
			IEnumerable<ScrapeResult> results = jScrape.ScrapeSingle(this.Document, selector);
			String nullResultMessage = String.Format("{0} : jQuerySelector did not return results - {1}", message, this.Document.title);
			Assert.IsNotNull(results, nullResultMessage);
			return results;
		}

		/// <summary>
		/// Does not contain attribute.
		/// </summary>
		public void NoAttr(String selector, String key, String value, String message)
		{
			IEnumerable<ScrapeResult> results = GetSelectorResults(selector, message);
			Boolean containsValidAttribute = ContainsValidAttribute(key, value, results);

			Assert.IsFalse(containsValidAttribute, message);
		}

		/// <summary>
		/// Does not contain an attribute.
		/// </summary>
		/// <param name="Selector">The jQuery selector to find the tag.</param>
		public void NoTag(String selector, String message)
		{
			IEnumerable<ScrapeResult> results = jScrape.ScrapeSingle(this.Document, selector);			
			Assert.IsNull(results, message);
		}

		/// <summary>
		/// Does not have the value.
		/// </summary>
		/// <param name="Selector">The jQuery selector to find the tag.</param>
		/// <param name="value">The value to test.</param>
		public void NoVal(String selector, String value, String message)
		{
			IEnumerable<ScrapeResult> results = GetSelectorResults(selector, message);
			Assert.IsFalse(ContainsValue(value, results), message);	
		}

		/// <summary>
		/// Use <see cref="AspNetDevelopmentServerAttribute"/> to create a url to test.
		/// </summary>
		/// <param name="relitiveUrl"></param>
		/// <param name="context"></param>
		/// <returns></returns>
		private Uri CreateAspNetDevelopmentServertUrl(String relitiveUrl, TestContext context)
		{
			// Check for valid arguments
			if(String.IsNullOrWhiteSpace(relitiveUrl))
				throw new ArgumentNullException("relitiveUrl");
			if(relitiveUrl.StartsWith("/") 
				|| relitiveUrl.StartsWith("htt", StringComparison.CurrentCultureIgnoreCase)) 
				throw new ArgumentException("Use a relitive Url");
			
			if(context == null) 
				throw new ArgumentNullException("context");

			// Find and create the url
			foreach (String key in context.Properties.Keys)
			{
				if(key.StartsWith("AspNetDevelopmentServer")) {
					return new Uri(context.Properties[key].ToString() + relitiveUrl);
				}
			}

			// If nothing is found lets make
			throw new InvalidOperationException("Could not find AspNetDevelopmentServer in context, make sure to use the use the AspNetDevelopmentServerAttribute.");
		}
	}
}
